package com.dz.core.component.aop;

import com.alibaba.fastjson.JSONObject;
import com.dz.core.annotation.ParamValidation;
import com.dz.core.component.exception.DzException;
import com.dz.core.constant.ResCode;
import com.dz.core.constant.ValidationTypeEnum;
import com.dz.core.dto.ReqBody;
import com.dz.core.dto.ResBody;
import com.google.common.base.Strings;
import io.jsonwebtoken.lang.Collections;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.lang.reflect.Method;
import java.util.*;

@Aspect
@Component
public class ParamValidationAop {

    private static final Logger logger = LoggerFactory.getLogger(ParamValidationAop.class);

    /**
     * 拦截所有带有@ParamValidation 注解的方法
     * @param joinPoint
     * controller层的public方法环绕处理
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Before(value = "@annotation(com.dz.core.annotation.ParamValidation) && args(reqBody,..)")
    public void doBeforeAdvice(JoinPoint joinPoint, ReqBody reqBody) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String objClassName = methodSignature.getDeclaringType().getName();
        Method method  = methodSignature.getMethod();
        String methodName = method.getName();
        logger.info(String.format("controller:{%s},method:{%s},params:{%s}",objClassName,methodName,reqBody.toString()));
        if(reqBody != null
                && (Strings.isNullOrEmpty(reqBody.getAppId())
                    || reqBody.getRnd() == null || reqBody.getTimestamp() == null)){
            throw new DzException(ResCode.PARAMETER_MISS);
        }
        validParams(reqBody,method);
    }


    /**
     * 验证请求参数正确性
     * @param reqBody
     * @param method
     * @return ValidationResult
     */
    private void validParams(ReqBody reqBody, Method method) throws DzException {
        //参数验证
        ParamValidation validation = method.getAnnotation(ParamValidation.class);
        if(null != validation){

//            Class  bean = validation.bean();
            Object tempObj = reqBody.getBody();
//            Object tempObj = JSONObject.toJavaObject(reqBody.getBody());
            String[] values = validation.value();//需要验证的参数
            ValidationTypeEnum vtem = validation.type();

            if(null == values || values.length <= 0 && !vtem.equals(ValidationTypeEnum.ALL)){
                throw new DzException(ResCode.SERVER_ERROR,"ParamValidation.value 必填："+method.getDeclaringClass() + "." + method.getName());
            }

            Map<String,String> errorMsg = validation(tempObj,values,vtem);
            if(!Collections.isEmpty(errorMsg)){
                throw new DzException(ResCode.PARAMETER_TYPE_MISMATCHING,JSONObject.toJSONString(errorMsg));
            }
        }
    }

    /**
     * 验证实体中参数正确性 返回验证信息
     * @param tempObj
     * @param values
     * @param vtem
     * @return
     */
    private Map<String,String> validation(Object tempObj,String[] values,ValidationTypeEnum vtem){
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<Object>> beanSet = validator.validate(tempObj);

        Map<String,String> errorMsg = null;
        if(!Collections.isEmpty(beanSet)){
            Set<String> valueSet = new HashSet<>(Arrays.asList(values));
            errorMsg = new HashMap<>();
            for (ConstraintViolation<Object> cv : beanSet){
                String property = cv.getPropertyPath().toString();
                String msg = cv.getMessage();
                switch (vtem) {
                    case ALL:
                        errorMsg.put(property, msg);
                        break;
                    case INCLUDE:
                        if (valueSet.contains(property)) {
                            errorMsg.put(property, msg);
                        }
                        break;
                    case EXCLUDE:
                        if (valueSet.contains(property)) {
                            continue;
                        }
                        errorMsg.put(property, msg);
                        break;
                }
            }
        }
        return errorMsg;
    }
}
